package xhttp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type httpx struct {
	headers         map[string]string
	chartset        string
	contentTypeName string
	//response    RespX
	isDebug bool
	timeout time.Duration
}

type RespX struct {
	header     http.Header
	b          []byte
	statusCode int
}

type Values map[string]interface{}

func (v Values) parseQuery(query interface{}) {

}

const (
	TYPE_FORM       = "TYPE_FORM"
	TYPE_JSON       = "TYPE_JSON"
	TYPE_URLENCODED = "TYPE_URLENCODED"
)

const (
	CONTENT_TYPE_FORM       = "multipart/form-data"
	CONTENT_TYPE_JSON       = "application/json"
	CONTENT_TYPE_URLENCODED = "application/x-www-form-urlencoded"
)

func Client() *httpx {
	x := httpx{}
	x.headers = map[string]string{}
	x.chartset = "charset=utf-8"
	x.timeout = 15 * time.Second
	return &x
}

func (x *httpx) TypeJson() *httpx {
	x.contentTypeName = TYPE_JSON
	x.SetHeader("Content-Type", CONTENT_TYPE_JSON+";"+x.chartset)
	return x
}

func (x *httpx) TypeForm() *httpx {
	x.contentTypeName = TYPE_FORM
	delete(x.headers, "Content-Type")
	return x
}

func (x *httpx) TypeUrlEncoded() *httpx {
	x.contentTypeName = TYPE_URLENCODED
	x.SetHeader("Content-Type", CONTENT_TYPE_URLENCODED+";"+x.chartset)
	return x
}

func (x *httpx) SetHeaders(kv map[string]string) *httpx {
	for key, val := range kv {
		x.headers[key] = val
	}

	return x
}

func (x *httpx) SetHeader(key string, val string) *httpx {
	x.headers[key] = val
	return x
}

func (x *httpx) Debug(isDebug bool) *httpx {
	x.isDebug = isDebug
	return x
}

func (x *httpx) println(a ...interface{}) (int, error) {
	if x.isDebug {
		return fmt.Println(a...) // goland 终端有bug 单行换行会有问题
	}
	return 0, nil
}

func (x *httpx) bodyFormat(body interface{}) *bytes.Buffer {

	bf := &bytes.Buffer{}

	x.println("content-type", x.contentTypeName)

	fmt.Println("Kind Kind", reflect.TypeOf(body).Kind())
	switch reflect.TypeOf(body).Kind() {
	case reflect.Map:

		if x.contentTypeName == TYPE_FORM {
			writer := multipart.NewWriter(bf)
			x.SetHeader("Content-Type", CONTENT_TYPE_FORM+"; boundary="+writer.Boundary())
			defer writer.Close()

			for key, val := range body.(map[string]interface{}) {
				if strings.Index(key, "@") == 0 {
					file, err := os.Open(key[1:])
					defer file.Close()
					if err == nil {
						if fileWrite, err := writer.CreateFormFile(key[1:], path.Base(file.Name())); err == nil {
							_, err = io.Copy(fileWrite, file)
						}
					}
				} else {

					_ = writer.WriteField(key, fmt.Sprint(val))
				}
			}
		} else if x.contentTypeName == TYPE_JSON {
			if b, err := json.Marshal(body); err == nil {
				bf.WriteString(string(b))
			} else {
				x.println(TYPE_JSON, err.Error())
			}
		} else if x.contentTypeName == TYPE_URLENCODED {
			//u := url.URL{}
			//q := u.Query()
			//q.Add("", "")

			//fmt.Println("type", reflect.TypeOf(body))

			query := []string{}
			if reflect.TypeOf(body).String() == "xhttp.Values" {
				for key, val := range body.(Values) {
					query = append(query, url.QueryEscape(key)+"="+url.QueryEscape(fmt.Sprint(val)))
				}
			} else {
				for key, val := range body.(map[string]interface{}) {
					query = append(query, url.QueryEscape(key)+"="+url.QueryEscape(fmt.Sprint(val)))
				}
			}

			bf.WriteString(strings.Join(query, "&"))
		}

		x.println("map", body)
	case reflect.Struct:
		if x.contentTypeName == TYPE_FORM {
			writer := multipart.NewWriter(bf)
			defer writer.Close()
			x.SetHeader("Content-Type", CONTENT_TYPE_FORM+"; boundary="+writer.Boundary())

			t := reflect.TypeOf(body)
			v := reflect.ValueOf(body)

			for i := 0; i < t.NumField(); i++ {
				var key = t.Field(i).Name
				var val = fmt.Sprint(v.Field(i).Interface())
				if strings.Index(key, "@") == 0 {
					file, err := os.Open(key[1:])
					defer file.Close()
					if err == nil {
						if fileWrite, err := writer.CreateFormFile(key[1:], path.Base(file.Name())); err == nil {
							_, err = io.Copy(fileWrite, file)
						}
					}
				} else {
					_ = writer.WriteField(key, fmt.Sprint(val))
				}
			}
		} else if x.contentTypeName == TYPE_JSON {
			if b, err := json.Marshal(body); err == nil {
				bf.WriteString(string(b))
			} else {
				x.println(TYPE_JSON, err.Error())
			}
		} else if x.contentTypeName == TYPE_URLENCODED {
			//u := url.URL{}
			//q := u.Query()
			//q.Add("", "")

			query := []string{}
			t := reflect.TypeOf(body)
			v := reflect.ValueOf(body)
			for i := 0; i < t.NumField(); i++ {

				query = append(query, url.QueryEscape(t.Field(i).Name)+"="+url.QueryEscape(fmt.Sprint(v.Field(i).Interface())))
			}

			bf.WriteString(strings.Join(query, "&"))
		}

		x.println("map", body)
	case reflect.String:
		bf.WriteString(body.(string))
		x.println("string", body)
	case reflect.Int:
		bf.WriteString(strconv.Itoa(body.(int)))
		x.println("string", body)
	}

	return bf
}

func (r *RespX) GetString() string {
	if len(r.b) > 0 {
		return string(r.b)
	}
	return ""
}

func (r *RespX) GetT(t interface{}) error {
	err := json.Unmarshal(r.b, &t)
	return err
}

func (x *httpx) Post(url string, body interface{}) (RespX, error) {

	if x.contentTypeName == "" {
		x.TypeJson()
	}

	client := http.Client{
		Timeout: x.timeout,
	}
	x.println("============HTTPX START=============")
	defer x.println("============HTTPX END=============")
	x.println("url:\t", url)
	request, err := http.NewRequest("POST", url, x.bodyFormat(body))
	response := RespX{}
	if err != nil {
		x.println("new request error:", err.Error())
		return response, err
	}

	tmp, _ := json.Marshal(x.headers)
	x.println("headers:\t", string(tmp))
	for key, val := range x.headers {
		request.Header.Set(key, val)
	}

	x.println("base body info:\t", body)
	//x.println("body formated info:\t", x.bodyFormat(body))

	if resp, err2 := client.Do(request); err2 == nil {
		defer resp.Body.Close()
		result, _ := ioutil.ReadAll(resp.Body)
		x.println("============================\nresponse:\n", string(result))
		response.header = resp.Header
		response.b = result
		response.statusCode = resp.StatusCode
		return response, nil
	} else {
		return response, err2
	}

}

func (x *httpx) Get(_url string, query ...Values) (RespX, error) {
	//resp, err := http.Get(url)
	//defer resp.Body.Close()
	//body, err := ioutil.ReadAll(resp.Body)
	client := http.Client{
		Timeout: x.timeout,
	}
	//defer client.CloseIdleConnections()
	// 拼接 query
	if len(query) > 0 {
		queryArr := []string{}
		for _, values := range query {
			for key, val := range values {
				queryArr = append(queryArr, url.QueryEscape(key)+"="+url.QueryEscape(fmt.Sprint(val)))
			}
		}
		if strings.Index(_url, "?") == -1 {
			_url += "?" + strings.Join(queryArr, "&")
		} else {
			_url += "&" + strings.Join(queryArr, "&")
		}
	}

	x.println("============HTTPX START=============")
	defer x.println("============HTTPX END=============")
	x.println("url:\t", _url)
	request, err := http.NewRequest("GET", _url, nil)
	response := RespX{}
	if err != nil {
		x.println("new request error:", err.Error())
		return response, err
	}
	//client.timeout.Round(1 * time.Second)

	//return x.response, nil
	tmp, _ := json.Marshal(x.headers)
	x.println("headers:\t", string(tmp))
	for key, val := range x.headers {
		request.Header.Set(key, val)
	}

	if resp, err := client.Do(request); err == nil {
		defer resp.Body.Close() // err == nil 时 无需close
		result, _ := ioutil.ReadAll(resp.Body)
		x.println("response:\t", fmt.Sprint(string(result)))
		//x.println()
		response.header = resp.Header
		response.b = result
		response.statusCode = resp.StatusCode
		return response, nil
	} else {
		fmt.Println(time.Now().String(), err.Error())
		return response, err
	}
}

//
//func Gets(url string, data url.Values) string {
//	resp, err := http.Get(url)
//	defer resp.Body.Close()
//	body, err := ioutil.ReadAll(resp.Body)
//	if err != nil {
//
//	}
//	return string(body)
//}
//
////var contentTypeName = new(contentType)
//
//func PostForms(url string, data url.Values) string {
//	resp, err := http.PostForm(url, data)
//	defer resp.Body.Close()
//	body, err := ioutil.ReadAll(resp.Body)
//	if err != nil {
//
//	}
//
//	return string(body)
//}
//
//func PostFormb(url string, data url.Values) []byte {
//	resp, err := http.PostForm(url, data)
//	defer resp.Body.Close()
//	body, err := ioutil.ReadAll(resp.Body)
//	if err != nil {
//
//	}
//	return body
//}
//
//func Getb(url string, data url.Values) []byte {
//	resp, err := http.Get(url)
//	defer resp.Body.Close()
//	body, err := ioutil.ReadAll(resp.Body)
//	if err != nil {
//
//	}
//	return body
//}
